package typechecking;

import java.util.HashMap;

import symbol.ClassTable;
import symbol.ErrorMsg;
import symbol.MethodTable;
import syntaxtree.And;
import syntaxtree.ArrayAssign;
import syntaxtree.ArrayLength;
import syntaxtree.ArrayLookup;
import syntaxtree.Assign;
import syntaxtree.Block;
import syntaxtree.BooleanType;
import syntaxtree.Call;
import syntaxtree.ClassDeclExtends;
import syntaxtree.ClassDeclSimple;
import syntaxtree.False;
import syntaxtree.Formal;
import syntaxtree.Identifier;
import syntaxtree.IdentifierExp;
import syntaxtree.IdentifierType;
import syntaxtree.If;
import syntaxtree.IntArrayType;
import syntaxtree.IntegerLiteral;
import syntaxtree.IntegerType;
import syntaxtree.LessThan;
import syntaxtree.MainClass;
import syntaxtree.MethodDecl;
import syntaxtree.Minus;
import syntaxtree.NewArray;
import syntaxtree.NewObject;
import syntaxtree.Not;
import syntaxtree.Plus;
import syntaxtree.Print;
import syntaxtree.Program;
import syntaxtree.This;
import syntaxtree.Times;
import syntaxtree.True;
import syntaxtree.VarDecl;
import syntaxtree.While;
import visitor.Visitor;

public class TypeCheckingVisitor implements Visitor {
        ClassTable currClass;
        MethodTable currMethod;
        ClassTable currClassExtend; // usado para percorrer quando classe extend outra
        HashMap<String, ClassTable> symbolTable;
        String type; // Uso pra saber o tipo de retorno entre as chamadas...
        public ErrorMsg error = new ErrorMsg();

        
        public void setSymbolTable(HashMap<String, ClassTable> c) {
                this.symbolTable = c;
                
        }
        
        @Override
        public void visit(Program n) {
                // TODO Auto-generated method stub
                n.m.accept(this);
                for(int i = 0; i < n.cl.size(); i++) n.cl.elementAt(i).accept(this);            
        }

        @Override
        public void visit(MainClass n) {
                // TODO Auto-generated method stub
                currClass = symbolTable.get(n.i1.s);
                currMethod = currClass.getMethodTable("main");
                n.s.accept(this);
        }

        @Override
        public void visit(ClassDeclSimple n) {
                // TODO Auto-generated method stub
                currClass = symbolTable.get(n.i.s); // pego a tabela da classe
                for(int i = 0; i < n.ml.size() ; i++) n.ml.elementAt(i).accept(this);
                currClass = null; // fim do escopo da classe
        }

        @Override
        public void visit(ClassDeclExtends n) {
                // TODO Auto-generated method stub
                currClass = symbolTable.get(n.i.s);
                //currClass.superClass = n.j.s;
                for(int i = 0; i < n.ml.size() ; i++) n.ml.elementAt(i).accept(this);
                currClass = null;
        }

        @Override
        public void visit(VarDecl n) {
                // TODO Auto-generated method stub
        }

        @Override
        public void visit(MethodDecl n) {
                // TODO Auto-generated method stub
                
                currMethod = currClass.getMethodTable(n.i.s); // seto a tabela do metodo
                for(int i = 0; i < n.sl.size() ; i++) n.sl.elementAt(i).accept(this);
                currMethod = null;
        }

        @Override
        public void visit(Formal n) {
                // TODO Auto-generated method stub
                
        }

        @Override
        public void visit(IntArrayType n) {
                // TODO Auto-generated method stub

        }

        @Override
        public void visit(BooleanType n) {
                // TODO Auto-generated method stub

        }

        @Override
        public void visit(IntegerType n) {
                // TODO Auto-generated method stub

        }

        @Override
        public void visit(IdentifierType n) {
                // TODO Auto-generated method stub
                if(!this.symbolTable.containsKey(n.s)) 
                        error.complain("IdentifierType: This type is not defined: "+n.s+ "\n"); 
        }

        @Override
        public void visit(Block n) {
                // TODO Auto-generated method stub
                for(int i = 0; i < n.sl.size() ; i++) n.sl.elementAt(i).accept(this);
        }

        @Override
        public void visit(If n) {
                // TODO Auto-generated method stub
                n.e.accept(this);
                if(!type.equals("bool")) error.complain("If: Evaluated expression is not a bool \n");  
                n.s1.accept(this);
                n.s2.accept(this);
        }

        @Override
        public void visit(While n) {
                // TODO Auto-generated method stub
                n.e.accept(this);
                if(!type.equals("bool")) error.complain("While: Evaluated expression is not a bool \n");  
                n.s.accept(this);
        }

        @Override
        public void visit(Print n) {
                // TODO Auto-generated method stub
                n.e.accept(this);
        }

        @Override
        public void visit(Assign n) {
                // TODO Auto-generated method stub
                        
                        String typeAux;
                        n.i.accept(this);
                        typeAux = type;
                        n.e.accept(this);
                        if(!(typeAux.equals(type)))
                                error.complain("Assign: Different types, Id: "+typeAux+" Exp: "+type+"\n");
        }

        @Override
        public void visit(ArrayAssign n) {
                // TODO Auto-generated method stub
                
                n.i.accept(this);
                if(!(type.equals("intarray")))
                        error.complain("ArrayAssign: The array type must be int.\n");
                
                n.e1.accept(this);
                if(!(type.equals("int")))
                        error.complain("ArrayAssign: The array index must be integer.\n");
                
                n.e2.accept(this);
                if(!(type.equals("int")))
                        error.complain("The expression must be integer.\n");
        }

        @Override
        public void visit(And n) {
                // TODO Auto-generated method stub
                n.e1.accept(this);
                if(!(type.equals("bool")))
                        error.complain("AndExpression: The operand must be boolean.\n");
                
                n.e2.accept(this);
                if(!(type.equals("bool")))
                        error.complain("AndExpression: The operand must be boolean.\n");
                type = "bool";
        }

        @Override
        public void visit(LessThan n) {
                // TODO Auto-generated method stub
                n.e1.accept(this);
                if(!(type.equals("int")))
                        error.complain("LessThanExpression: The operand must be integer.\n");
                
                n.e2.accept(this);
                if(!(type.equals("int")))
                        error.complain("LessThanExpression: The operand must be integer.\n");
                type = "bool";
        }

        @Override
        public void visit(Plus n) {
                // TODO Auto-generated method stub
                n.e1.accept(this);
                if(!(type.equals("int")))
                        error.complain("PlusExpression: The operand must be integer.\n");
                
                n.e2.accept(this);
                if(!(type.equals("int")))
                        error.complain("PlusExpression: The operand must be integer.\n");
                type = "int";
        }

        @Override
        public void visit(Minus n) {
                // TODO Auto-generated method stub
                n.e1.accept(this);
                if(!(type.equals("int")))
                        error.complain("MinusExpression: The operand must be integer.\n");
                
                n.e2.accept(this);
                if(!(type.equals("int")))
                        error.complain("MinusExpression: The operand must be integer.\n");
                type = "int";
        }

        @Override
        public void visit(Times n) {
                // TODO Auto-generated method stub
                n.e1.accept(this);
                if(!(type.equals("int")))
                        error.complain("TimesExpression: The operand must be integer.\n");
                
                n.e2.accept(this);
                if(!(type.equals("int")))
                        error.complain("TimesExpression: The operand must be integer.\n");
                type = "int";
        }

        @Override
        public void visit(ArrayLookup n) {
                // TODO Auto-generated method stub
                n.e1.accept(this);
                if(!(type.equals("intarray")))
                        error.complain("ArrayLookup: The array type must be int.\n");
                
                n.e2.accept(this);
                if(!(type.equals("int")))
                        error.complain("ArrayLookup: The array index must be integer.\n");
                type = "int";
        }

        @Override
        public void visit(ArrayLength n) {
                // TODO Auto-generated method stub
                n.e.accept(this);
                if(!(type.equals("intarray")))
                        error.complain("ArrayLength: The array type must be int.\n");
                type = "int";
        }

        @Override
        public void visit(Call n) {
                // TODO Auto-generated method stub
                ClassTable classAux;
                ClassTable classExtended;
                ClassTable classExtendedParam;
                MethodTable method;

                n.e.accept(this);
                if(symbolTable.containsKey(type)){
                        classAux = symbolTable.get(type);
                        if(classAux.containsMethod(n.i.s)){
                                method = classAux.getMethodTable(n.i.s);
                                for(int i=0;i<method.getParameterSize();i++){
                                        n.el.elementAt(i).accept(this);

                                        if(symbolTable.containsKey(method.getParameterType(i))){
                                                classExtendedParam = symbolTable.get(type);
                                                while(classExtendedParam.superClass!=null){
                                                        if(type.equals(method.getParameterType(i))) break;
                                                        else{
                                                                classExtendedParam = symbolTable.get(classExtendedParam.superClass);
                                                                type = classExtendedParam.getClassName();
                                                        }
                                                }
                                        }
                                        
                                        if(!(type.equals(method.getParameterType(i))))
                                        error.complain("Call: The type of the parameter " + i +" must be " + method.getParameterType(i) +".\n");
                                }
                                type = method.getReturnType();
                        }
                        else if(classAux.superClass!=null){
                                while(this.currClassExtend != null){
                                        classExtended = symbolTable.get(classAux.superClass);
                                        if(classExtended.containsMethod(n.i.s)) {
                                                method = classAux.getMethodTable(n.i.s);
                                                for(int i=0;i<method.getParameterSize();i++){
                                                        n.el.elementAt(i).accept(this);
                                                        if(symbolTable.containsKey(method.getParameterType(i))){

                                                                classExtendedParam = symbolTable.get(type);
                                                                while(classExtendedParam.superClass!=null){
                                                                        if(type.equals(method.getParameterType(i))) break;
                                                                        else{
                                                                                classExtendedParam = symbolTable.get(classExtendedParam.superClass);
                                                                                type = classExtendedParam.superClass;
                                                                                System.out.print(type);
                                                                        }
                                                                }
                                                        }
                                                        
                                                        if(!(type.equals(method.getParameterType(i))))
                                                                error.complain("Call: The type of the parameter " + i +" must be " + method.getParameterType(i) +".\n");

                                                }
                                                type = method.getReturnType();
                                                break;
                                        }
                                }
                        }
                        else error.complain("Call: This method is not defined: "+n.i.s+ "\n");
                }
                else error.complain("Call: This type is not defined \n");
        }

        @Override
        public void visit(IntegerLiteral n) {
                // TODO Auto-generated method stub
                type = "int";
        }

        @Override
        public void visit(True n) {
                // TODO Auto-generated method stub
                type = "bool";
        }

        @Override
        public void visit(False n) {
                // TODO Auto-generated method stub
                type = "bool";
        }

        @Override
        public void visit(IdentifierExp n) {
                // TODO Auto-generated method stub
                ClassTable classExtended;
                if(this.currMethod.containsParameter(n.s)){
                        type = currMethod.getParameterType(n.s);
                }
                else 
                        if(this.currMethod.containsVar(n.s)){
                                type = currMethod.getVarType(n.s);
                        }
                        else 
                                if(this.currClass.containsVar(n.s)){
                                        type = currClass.getFieldType(n.s);
                                }
                                else if(currClass.superClass!=null){
                                                classExtended = symbolTable.get(currClass.superClass);
                                                while(classExtended != null){
                                                        if(classExtended.containsVar(n.s)) {
                                                                type = classExtended.getFieldType(n.s);
                                                                break;
                                                        }
                                                        classExtended = symbolTable.get(classExtended.superClass);
                                                }
                                        }
                                        else error.complain("This variable is not defined: "+n.s+ "\n");
        }

        @Override
        public void visit(This n) {
                // TODO Auto-generated method stub

                type = currClass.getClassName();
        }

        @Override
        public void visit(NewArray n) {
                // TODO Auto-generated method stub
                n.e.accept(this);
                if(!(type.equals("int")))
                        error.complain("NewArray: The array index must be integer.\n");
                type = "intarray";
        }

        @Override
        public void visit(NewObject n) {
                // TODO Auto-generated method stub
                if(!this.symbolTable.containsKey(n.i.s)) 
                        error.complain("NewObject: This class is not defined: "+n.i.s+ "\n"); 
                type = n.i.s;
        }

        @Override
        public void visit(Not n) {
                // TODO Auto-generated method stub
                n.e.accept(this);
                if(!(type.equals("bool")))
                        error.complain("NotExpression: The operand must be boolean.\n");
                type = "bool";
        }

        @Override
        public void visit(Identifier n) {
                // TODO Auto-generated method stub
                ClassTable classExtended;
                if(this.currMethod.containsParameter(n.s)){
                        type = currMethod.getParameterType(n.s);
                }
                else 
                        if(this.currMethod.containsVar(n.s)){
                                type = currMethod.getVarType(n.s);
                        }
                        else 
                                if(this.currClass.containsVar(n.s)){
                                        type = currClass.getFieldType(n.s);
                                }
                                else if(currClass.superClass!=null){
                                                classExtended = symbolTable.get(currClass.superClass);
                                                while(classExtended != null){
                                                        if(classExtended.containsVar(n.s)) {
                                                                type = classExtended.getFieldType(n.s);
                                                                break;
                                                        }
                                                        classExtended = symbolTable.get(classExtended.superClass);
                                                }
                                        }
                                        else error.complain("This variable is not defined: "+n.s+ "\n");
        }
}